library(chemhelper) #for sim_*(), plot_pca(), plot_plsda(), get_VIP(), and get_loadings()
library(tidyverse)
library(ropls) #for opls(), which does pca and pls-da
library(cowplot) #for plot_grid()
theme_set(theme_gray())
library(iheatmapr) #for correlation heatmaps

No discriminating variables

The code chunk below creates a data frame with a grouping variable with two groups (a and b) with n=10 per group, 5 variables that don’t covary, and two groups of 10 variables that moderately covary.

# myseed <- runif(1, 0, 1000)
myseed <- 401.6368
df <- data_frame(group = rep(c("a", "b"), each = 10))
no_discr <- df %>%
  sim_covar(5, var = 1, cov = 0, name = "uncorr", seed = myseed) %>% 
  sim_covar(10, var = 1, cov = 0.5, name = "corrA", seed = myseed + 1) %>% 
  sim_covar(10, var = 1, cov = 0.5, name = "corrB", seed = myseed + 2)

This heatmap shows the correlation structure.

no_discr %>% 
  select(-group) %>% 
  cor() %>% 
  iheatmap(row_labels = TRUE, col_labels = TRUE, name = "cor")
no_discr.cor <- no_discr %>% 
  select(-group) %>% 
  cor() %>%
  as.data.frame() %>% 
  rownames_to_column(var = "row") %>% 
  gather(-row, key = col, value = cor)
cor1 <- ggplot(no_discr.cor, aes(x = col, y = row, fill = cor)) +
  geom_tile() +
  scale_fill_gradient2("Correlation Coeficient") +
  labs(x = NULL, y = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90), legend.position = "top")

Then I perform PCA and PLS-DA on this dataset, forcing both models to have two axes (for plotting). The PCA score plot shows no separation and the first two axes explain more than half of the variation in the data. The PLS-DA score plot shows slight separation of the groups (even though there is none). It’s a pretty terrible model with a negative Q2 value and a low R2 value.

pca1.plot <- plot_pca(pca1, no_discr$group, annotate = "subtitle")
pls1.plot <- plot_plsda(pls1, annotate = "subtitle")
plot_grid(cor1,
          pca1.plot + theme(legend.position = "NULL"),
          pls1.plot + theme(legend.position = "NULL"), nrow = 1, ncol = 3)

Few discriminating variables

Keeping the total number of observations and variables the same, and using the same random seed, I create a second data set with 5 variables that do not covary within groups, but differ in their means between groups.

# discr <- df %>% 
#   sim_covar(5, var = 1, cov = 0, name = "uncorr", seed = myseed) %>% 
#   sim_covar(8, var = 1, cov = 0.55, name = "corrA", seed = myseed + 1) %>% 
#   sim_covar(7, var = 1, cov = 0.55, name = "corrB", seed = myseed + 2) %>% 
#   sim_discr(5, var = 1, cov = 0, group_means = c(-1, 1), name = "discr", seed = myseed + 3)
discr <- df %>% 
  sim_covar(10, var = 1, cov = 0.55, name = "corrA", seed = myseed + 1) %>% 
  sim_covar(10, var = 1, cov = 0.55, name = "corrB", seed = myseed + 2) %>% 
  sim_discr(5, var = 1, cov = 0, group_means = c(-1, 1), name = "discr", seed = myseed + 3)

This shows the correlation structure of the data. Because the discriminating variables have different means in the two groups, they are correlated about as strongly as the covarying variables.

discr %>% 
  select(-group) %>% 
  cor() %>% 
  iheatmap(row_labels = TRUE, col_labels = TRUE, name = "cor")
discr.cor <- discr %>% 
  select(-group) %>% 
  cor() %>%
  as.data.frame() %>% 
  rownames_to_column(var = "row") %>% 
  gather(-row, key = col, value = cor)
cor2 <- ggplot(discr.cor, aes(x = col, y = row, fill = cor)) +
  geom_tile() +
  scale_fill_gradient2("Correlation Coeficient") +
  labs(x = NULL, y = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90), legend.position = "top") +
  annotate(geom = "text", x = 4, y = -1, label = "test")
cor2

The PCA score plot still shows no real separation because the discriminating variables do not contribute much to the total covariation in the data. The PLS-DA plot shows very strong separation, despite the differences between the groups being only due to 5 out of 25 variables. The PLS-DA model performs well, with high R2 and Q2 values

pca2.plot <- plot_pca(pca2, discr$group, annotate = "subtitle")
pls2.plot <- plot_plsda(pls2, annotate = "subtitle")
plot_grid(cor2, pca2.plot + theme(legend.position = "NULL"),
          pls2.plot + theme(legend.position = "NULL"), nrow = 1, ncol = 3)

VIP scores from the PLS-DA model identify all 5 discriminating variables as being important for separating the groups (VIP > 1). Using the VIP > 1 cutoff, it also spurriously identifies two of the other variables as being important. Discriminating variables are not strongly correlated with the first two axes of the PCA.

VIPs <- get_VIP(pls2) %>%
  arrange(desc(VIP))
pca.load <- get_loadings(pca2) %>%
  select(Variable, p1, p2) %>%
  arrange(desc(abs(p1)))

Save plots and tables

fig1 <- plot_grid(cor1,
          pca1.plot + theme(legend.position = "NULL") + labs(title = ""),
          pls1.plot + theme(legend.position = "NULL") + labs(title = ""),
          cor2,
          pca2.plot + theme(legend.position = "NULL") + labs(title = ""),
          pls2.plot + theme(legend.position = "NULL") + labs(title = ""),
          ncol = 3, nrow = 2, labels = "AUTO")
save_plot("figs/fig1.png", fig1, ncol = 3, nrow = 2)
table1 <- full_join(VIPs, pca.load) %>%
  arrange(Variable) %>% 
  rename(`PLS-DA VIP score` = VIP, `PC1 loading` = p1, `PC2 loading` = p2) %>% 
  mutate_if(is.double, ~round(.,4))
Joining, by = "Variable"
table1
write_excel_csv(table1, "figs/table1.csv")

Red Herring

Large variation with weak discrimination plus small variation with large discrimination

herring <- df %>% 
  sim_discr(p = 10, var = 1, cov = 0.5, group_means = c(-0.5, 0.5), name = "highvar") %>% 
  sim_discr(p = 10, var = 1, cov = 0, group_means = c(-1, 1), name = "lowvar") %>% 
  sim_covar(p = 5, var = 1, cov = 0, name = "noise")
herring %>% 
  select(-group) %>% 
  cor() %>% 
  iheatmap(row_labels = TRUE, col_labels = TRUE, name = "cor")
herring.cor <- herring %>% 
  select(-group) %>% 
  cor() %>%
  as.data.frame() %>% 
  rownames_to_column(var = "row") %>% 
  gather(-row, key = col, value = cor)
cor3 <- ggplot(herring.cor, aes(x = col, y = row, fill = cor)) +
  geom_tile() +
  scale_fill_gradient2("Correlation Coeficient") +
  labs(x = NULL, y = NULL) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90), legend.position = "top")
pca3 <- opls(select(herring, -group), plot = FALSE)
PCA
20 samples x 25 variables
standard scaling of predictors
pls3 <- opls(select(herring, -group), no_discr$group, plot = FALSE, predI = 2, permI = 500)
PLS-DA
20 samples x 25 variables and 1 response
standard scaling of predictors and response(s)
pca3.plot <- plot_pca(pca3, herring$group, annotate = "subtitle")
pls3.plot <- plot_plsda(pls3, annotate = "subtitle")
Joining, by = "sample"
plot_grid(cor3,
          pca3.plot + theme(legend.position = "NULL"),
          pls3.plot + theme(legend.position = "NULL"), nrow = 1, ncol = 3)

VIPs3 <- get_VIP(pls3) %>%
  arrange(desc(VIP))
pca3.load <- get_loadings(pca3) %>%
  select(Variable, p1, p2) %>%
  arrange(desc(abs(p1)))
table3 <- full_join(VIPs3, pca3.load) %>%
  arrange(Variable) %>% 
  rename(`PLS-DA VIP score` = VIP, `PC1 loading` = p1, `PC2 loading` = p2) %>% 
  mutate_if(is.double, ~round(.,4))
Joining, by = "Variable"
table3 %>% arrange(desc(`PLS-DA VIP score`))
table3 %>% arrange(desc(`PC1 loading`))
# write_excel_csv(table1, "figs/table1.csv")
LS0tCnRpdGxlOiAiUENBIHZzLiBQTFMgd2l0aCBubyBhbmQgZmV3IGRpc2NyaW1pbmF0aW5nIHZhcmlhYmxlcyIKYXV0aG9yOiAiRXJpYyBSLiBTY290dCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNoZW1oZWxwZXIpICNmb3Igc2ltXyooKSwgcGxvdF9wY2EoKSwgcGxvdF9wbHNkYSgpLCBnZXRfVklQKCksIGFuZCBnZXRfbG9hZGluZ3MoKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyb3BscykgI2ZvciBvcGxzKCksIHdoaWNoIGRvZXMgcGNhIGFuZCBwbHMtZGEKbGlicmFyeShjb3dwbG90KSAjZm9yIHBsb3RfZ3JpZCgpCnRoZW1lX3NldCh0aGVtZV9ncmF5KCkpCmxpYnJhcnkoaWhlYXRtYXByKSAjZm9yIGNvcnJlbGF0aW9uIGhlYXRtYXBzCmBgYAoKIyBObyBkaXNjcmltaW5hdGluZyB2YXJpYWJsZXMKClRoZSBjb2RlIGNodW5rIGJlbG93IGNyZWF0ZXMgYSBkYXRhIGZyYW1lIHdpdGggYSBncm91cGluZyB2YXJpYWJsZSB3aXRoIHR3byBncm91cHMgKGEgYW5kIGIpIHdpdGggbj0xMCBwZXIgZ3JvdXAsIDUgdmFyaWFibGVzIHRoYXQgZG9uJ3QgY292YXJ5LCBhbmQgdHdvIGdyb3VwcyBvZiAxMCB2YXJpYWJsZXMgdGhhdCBtb2RlcmF0ZWx5IGNvdmFyeS4KCmBgYHtyfQojIG15c2VlZCA8LSBydW5pZigxLCAwLCAxMDAwKQpteXNlZWQgPC0gNDAxLjYzNjgKCmRmIDwtIGRhdGFfZnJhbWUoZ3JvdXAgPSByZXAoYygiYSIsICJiIiksIGVhY2ggPSAxMCkpCm5vX2Rpc2NyIDwtIGRmICU+JQogIHNpbV9jb3Zhcig1LCB2YXIgPSAxLCBjb3YgPSAwLCBuYW1lID0gInVuY29yciIsIHNlZWQgPSBteXNlZWQpICU+JSAKICBzaW1fY292YXIoMTAsIHZhciA9IDEsIGNvdiA9IDAuNSwgbmFtZSA9ICJjb3JyQSIsIHNlZWQgPSBteXNlZWQgKyAxKSAlPiUgCiAgc2ltX2NvdmFyKDEwLCB2YXIgPSAxLCBjb3YgPSAwLjUsIG5hbWUgPSAiY29yckIiLCBzZWVkID0gbXlzZWVkICsgMikKYGBgCgpUaGlzIGhlYXRtYXAgc2hvd3MgdGhlIGNvcnJlbGF0aW9uIHN0cnVjdHVyZS4KCmBgYHtyfQpub19kaXNjciAlPiUgCiAgc2VsZWN0KC1ncm91cCkgJT4lIAogIGNvcigpICU+JSAKICBpaGVhdG1hcChyb3dfbGFiZWxzID0gVFJVRSwgY29sX2xhYmVscyA9IFRSVUUsIG5hbWUgPSAiY29yIikKYGBgCmBgYHtyfQpub19kaXNjci5jb3IgPC0gbm9fZGlzY3IgJT4lIAogIHNlbGVjdCgtZ3JvdXApICU+JSAKICBjb3IoKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAicm93IikgJT4lIAogIGdhdGhlcigtcm93LCBrZXkgPSBjb2wsIHZhbHVlID0gY29yKQoKY29yMSA8LSBnZ3Bsb3Qobm9fZGlzY3IuY29yLCBhZXMoeCA9IGNvbCwgeSA9IHJvdywgZmlsbCA9IGNvcikpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIoIkNvcnJlbGF0aW9uIENvZWZpY2llbnQiKSArCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApLCBsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgpUaGVuIEkgcGVyZm9ybSBQQ0EgYW5kIFBMUy1EQSBvbiB0aGlzIGRhdGFzZXQsIGZvcmNpbmcgYm90aCBtb2RlbHMgdG8gaGF2ZSB0d28gYXhlcyAoZm9yIHBsb3R0aW5nKS4gIFRoZSBQQ0Egc2NvcmUgcGxvdCBzaG93cyBubyBzZXBhcmF0aW9uIGFuZCB0aGUgZmlyc3QgdHdvIGF4ZXMgZXhwbGFpbiBtb3JlIHRoYW4gaGFsZiBvZiB0aGUgdmFyaWF0aW9uIGluIHRoZSBkYXRhLiAgVGhlIFBMUy1EQSBzY29yZSBwbG90IHNob3dzIHNsaWdodCBzZXBhcmF0aW9uIG9mIHRoZSBncm91cHMgKGV2ZW4gdGhvdWdoIHRoZXJlIGlzIG5vbmUpLiAgSXQncyBhIHByZXR0eSB0ZXJyaWJsZSBtb2RlbCB3aXRoIGEgbmVnYXRpdmUgUV4yXiB2YWx1ZSBhbmQgYSBsb3cgUl4yXiB2YWx1ZS4KCmBgYHtyIGluY2x1ZGU9RkFMU0V9CnBjYTEgPC0gb3BscyhzZWxlY3Qobm9fZGlzY3IsIC1ncm91cCksIHBsb3QgPSBGQUxTRSkKcGxzMSA8LSBvcGxzKHNlbGVjdChub19kaXNjciwgLWdyb3VwKSwgbm9fZGlzY3IkZ3JvdXAsIHBsb3QgPSBGQUxTRSwgcHJlZEkgPSAyLCBwZXJtSSA9IDUwMCkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwY2ExLnBsb3QgPC0gcGxvdF9wY2EocGNhMSwgbm9fZGlzY3IkZ3JvdXAsIGFubm90YXRlID0gInN1YnRpdGxlIikKcGxzMS5wbG90IDwtIHBsb3RfcGxzZGEocGxzMSwgYW5ub3RhdGUgPSAic3VidGl0bGUiKQpwbG90X2dyaWQoY29yMSwKICAgICAgICAgIHBjYTEucGxvdCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJOVUxMIiksCiAgICAgICAgICBwbHMxLnBsb3QgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTlVMTCIpLCBucm93ID0gMSwgbmNvbCA9IDMpCmBgYAoKIyBGZXcgZGlzY3JpbWluYXRpbmcgdmFyaWFibGVzCgpLZWVwaW5nIHRoZSB0b3RhbCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGFuZCB2YXJpYWJsZXMgdGhlIHNhbWUsIGFuZCB1c2luZyB0aGUgc2FtZSByYW5kb20gc2VlZCwgSSBjcmVhdGUgYSBzZWNvbmQgZGF0YSBzZXQgd2l0aCA1IHZhcmlhYmxlcyB0aGF0IGRvIG5vdCBjb3Zhcnkgd2l0aGluIGdyb3VwcywgYnV0IGRpZmZlciBpbiB0aGVpciBtZWFucyBiZXR3ZWVuIGdyb3Vwcy4KCmBgYHtyfQojIGRpc2NyIDwtIGRmICU+JSAKIyAgIHNpbV9jb3Zhcig1LCB2YXIgPSAxLCBjb3YgPSAwLCBuYW1lID0gInVuY29yciIsIHNlZWQgPSBteXNlZWQpICU+JSAKIyAgIHNpbV9jb3Zhcig4LCB2YXIgPSAxLCBjb3YgPSAwLjU1LCBuYW1lID0gImNvcnJBIiwgc2VlZCA9IG15c2VlZCArIDEpICU+JSAKIyAgIHNpbV9jb3Zhcig3LCB2YXIgPSAxLCBjb3YgPSAwLjU1LCBuYW1lID0gImNvcnJCIiwgc2VlZCA9IG15c2VlZCArIDIpICU+JSAKIyAgIHNpbV9kaXNjcig1LCB2YXIgPSAxLCBjb3YgPSAwLCBncm91cF9tZWFucyA9IGMoLTEsIDEpLCBuYW1lID0gImRpc2NyIiwgc2VlZCA9IG15c2VlZCArIDMpCmRpc2NyIDwtIGRmICU+JSAKICBzaW1fY292YXIoMTAsIHZhciA9IDEsIGNvdiA9IDAuNTUsIG5hbWUgPSAiY29yckEiLCBzZWVkID0gbXlzZWVkICsgMSkgJT4lIAogIHNpbV9jb3ZhcigxMCwgdmFyID0gMSwgY292ID0gMC41NSwgbmFtZSA9ICJjb3JyQiIsIHNlZWQgPSBteXNlZWQgKyAyKSAlPiUgCiAgc2ltX2Rpc2NyKDUsIHZhciA9IDEsIGNvdiA9IDAsIGdyb3VwX21lYW5zID0gYygtMSwgMSksIG5hbWUgPSAiZGlzY3IiLCBzZWVkID0gbXlzZWVkICsgMykKYGBgCgpUaGlzIHNob3dzIHRoZSBjb3JyZWxhdGlvbiBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEuICBCZWNhdXNlIHRoZSBkaXNjcmltaW5hdGluZyB2YXJpYWJsZXMgaGF2ZSBkaWZmZXJlbnQgbWVhbnMgaW4gdGhlIHR3byBncm91cHMsIHRoZXkgYXJlIGNvcnJlbGF0ZWQgYWJvdXQgYXMgc3Ryb25nbHkgYXMgdGhlIGNvdmFyeWluZyB2YXJpYWJsZXMuCgpgYGB7cn0KZGlzY3IgJT4lIAogIHNlbGVjdCgtZ3JvdXApICU+JSAKICBjb3IoKSAlPiUgCiAgaWhlYXRtYXAocm93X2xhYmVscyA9IFRSVUUsIGNvbF9sYWJlbHMgPSBUUlVFLCBuYW1lID0gImNvciIpCmBgYApgYGB7cn0KZGlzY3IuY29yIDwtIGRpc2NyICU+JSAKICBzZWxlY3QoLWdyb3VwKSAlPiUgCiAgY29yKCkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICByb3duYW1lc190b19jb2x1bW4odmFyID0gInJvdyIpICU+JSAKICBnYXRoZXIoLXJvdywga2V5ID0gY29sLCB2YWx1ZSA9IGNvcikKCmNvcjIgPC0gZ2dwbG90KGRpc2NyLmNvciwgYWVzKHggPSBjb2wsIHkgPSByb3csIGZpbGwgPSBjb3IpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKCJDb3JyZWxhdGlvbiBDb2VmaWNpZW50IikgKwogIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSwgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpICsKICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCB4ID0gNCwgeSA9IC0xLCBsYWJlbCA9ICJ0ZXN0IikKY29yMgpgYGAKVGhlIFBDQSBzY29yZSBwbG90IHN0aWxsIHNob3dzIG5vIHJlYWwgc2VwYXJhdGlvbiBiZWNhdXNlIHRoZSBkaXNjcmltaW5hdGluZyB2YXJpYWJsZXMgZG8gbm90IGNvbnRyaWJ1dGUgbXVjaCB0byB0aGUgdG90YWwgY292YXJpYXRpb24gaW4gdGhlIGRhdGEuICBUaGUgUExTLURBIHBsb3Qgc2hvd3MgdmVyeSBzdHJvbmcgc2VwYXJhdGlvbiwgZGVzcGl0ZSB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgZ3JvdXBzIGJlaW5nIG9ubHkgZHVlIHRvIDUgb3V0IG9mIDI1IHZhcmlhYmxlcy4gIFRoZSBQTFMtREEgbW9kZWwgcGVyZm9ybXMgd2VsbCwgd2l0aCBoaWdoIFJeMl4gYW5kIFFeMl4gdmFsdWVzCgpgYGB7ciBpbmNsdWRlPUZBTFNFfQpwY2EyIDwtIG9wbHMoc2VsZWN0KGRpc2NyLCAtZ3JvdXApLCBwbG90ID0gRkFMU0UpCnBsczIgPC0gb3BscyhzZWxlY3QoZGlzY3IsIC1ncm91cCksIGRpc2NyJGdyb3VwLCBwbG90ID0gRkFMU0UsIHByZWRJID0gMiwgcGVybUkgPSA1MDApCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGNhMi5wbG90IDwtIHBsb3RfcGNhKHBjYTIsIGRpc2NyJGdyb3VwLCBhbm5vdGF0ZSA9ICJzdWJ0aXRsZSIpCnBsczIucGxvdCA8LSBwbG90X3Bsc2RhKHBsczIsIGFubm90YXRlID0gInN1YnRpdGxlIikKcGxvdF9ncmlkKGNvcjIsIHBjYTIucGxvdCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJOVUxMIiksCiAgICAgICAgICBwbHMyLnBsb3QgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTlVMTCIpLCBucm93ID0gMSwgbmNvbCA9IDMpCmBgYAoKVklQIHNjb3JlcyBmcm9tIHRoZSBQTFMtREEgbW9kZWwgaWRlbnRpZnkgYWxsIDUgZGlzY3JpbWluYXRpbmcgdmFyaWFibGVzIGFzIGJlaW5nIGltcG9ydGFudCBmb3Igc2VwYXJhdGluZyB0aGUgZ3JvdXBzIChWSVAgPiAxKS4gIFVzaW5nIHRoZSBWSVAgPiAxIGN1dG9mZiwgaXQgYWxzbyBzcHVycmlvdXNseSBpZGVudGlmaWVzIHR3byBvZiB0aGUgb3RoZXIgdmFyaWFibGVzIGFzIGJlaW5nIGltcG9ydGFudC4gIERpc2NyaW1pbmF0aW5nIHZhcmlhYmxlcyBhcmUgbm90IHN0cm9uZ2x5IGNvcnJlbGF0ZWQgd2l0aCB0aGUgZmlyc3QgdHdvIGF4ZXMgb2YgdGhlIFBDQS4KCmBgYHtyfQpWSVBzIDwtIGdldF9WSVAocGxzMikgJT4lCiAgYXJyYW5nZShkZXNjKFZJUCkpCgpwY2EubG9hZCA8LSBnZXRfbG9hZGluZ3MocGNhMikgJT4lCiAgc2VsZWN0KFZhcmlhYmxlLCBwMSwgcDIpICU+JQogIGFycmFuZ2UoZGVzYyhhYnMocDEpKSkKYGBgCgojIFNhdmUgcGxvdHMgYW5kIHRhYmxlcwoKYGBge3J9CmZpZzEgPC0gcGxvdF9ncmlkKGNvcjEsCiAgICAgICAgICBwY2ExLnBsb3QgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTlVMTCIpICsgbGFicyh0aXRsZSA9ICIiKSwKICAgICAgICAgIHBsczEucGxvdCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJOVUxMIikgKyBsYWJzKHRpdGxlID0gIiIpLAogICAgICAgICAgY29yMiwKICAgICAgICAgIHBjYTIucGxvdCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJOVUxMIikgKyBsYWJzKHRpdGxlID0gIiIpLAogICAgICAgICAgcGxzMi5wbG90ICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIk5VTEwiKSArIGxhYnModGl0bGUgPSAiIiksCiAgICAgICAgICBuY29sID0gMywgbnJvdyA9IDIsIGxhYmVscyA9ICJBVVRPIikKc2F2ZV9wbG90KCJmaWdzL2ZpZzEucG5nIiwgZmlnMSwgbmNvbCA9IDMsIG5yb3cgPSAyKQpgYGAKCmBgYHtyfQp0YWJsZTEgPC0gZnVsbF9qb2luKFZJUHMsIHBjYS5sb2FkKSAlPiUKICBhcnJhbmdlKFZhcmlhYmxlKSAlPiUgCiAgcmVuYW1lKGBQTFMtREEgVklQIHNjb3JlYCA9IFZJUCwgYFBDMSBsb2FkaW5nYCA9IHAxLCBgUEMyIGxvYWRpbmdgID0gcDIpICU+JSAKICBtdXRhdGVfaWYoaXMuZG91YmxlLCB+cm91bmQoLiw0KSkKdGFibGUxCndyaXRlX2V4Y2VsX2Nzdih0YWJsZTEsICJmaWdzL3RhYmxlMS5jc3YiKQpgYGAKCiMgUmVkIEhlcnJpbmcKTGFyZ2UgdmFyaWF0aW9uIHdpdGggd2VhayBkaXNjcmltaW5hdGlvbiBwbHVzIHNtYWxsIHZhcmlhdGlvbiB3aXRoIGxhcmdlIGRpc2NyaW1pbmF0aW9uCgpgYGB7cn0KaGVycmluZyA8LSBkZiAlPiUgCiAgc2ltX2Rpc2NyKHAgPSAxMCwgdmFyID0gMSwgY292ID0gMC41LCBncm91cF9tZWFucyA9IGMoLTAuNSwgMC41KSwgbmFtZSA9ICJoaWdodmFyIikgJT4lIAogIHNpbV9kaXNjcihwID0gMTAsIHZhciA9IDEsIGNvdiA9IDAsIGdyb3VwX21lYW5zID0gYygtMSwgMSksIG5hbWUgPSAibG93dmFyIikgJT4lIAogIHNpbV9jb3ZhcihwID0gNSwgdmFyID0gMSwgY292ID0gMCwgbmFtZSA9ICJub2lzZSIpCmBgYAoKYGBge3J9CmhlcnJpbmcgJT4lIAogIHNlbGVjdCgtZ3JvdXApICU+JSAKICBjb3IoKSAlPiUgCiAgaWhlYXRtYXAocm93X2xhYmVscyA9IFRSVUUsIGNvbF9sYWJlbHMgPSBUUlVFLCBuYW1lID0gImNvciIpCmBgYApgYGB7cn0KaGVycmluZy5jb3IgPC0gaGVycmluZyAlPiUgCiAgc2VsZWN0KC1ncm91cCkgJT4lIAogIGNvcigpICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJyb3ciKSAlPiUgCiAgZ2F0aGVyKC1yb3csIGtleSA9IGNvbCwgdmFsdWUgPSBjb3IpCgpjb3IzIDwtIGdncGxvdChoZXJyaW5nLmNvciwgYWVzKHggPSBjb2wsIHkgPSByb3csIGZpbGwgPSBjb3IpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKCJDb3JyZWxhdGlvbiBDb2VmaWNpZW50IikgKwogIGxhYnMoeCA9IE5VTEwsIHkgPSBOVUxMKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSwgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCmBgYAoKCmBgYHtyfQpwY2EzIDwtIG9wbHMoc2VsZWN0KGhlcnJpbmcsIC1ncm91cCksIHBsb3QgPSBGQUxTRSkKcGxzMyA8LSBvcGxzKHNlbGVjdChoZXJyaW5nLCAtZ3JvdXApLCBub19kaXNjciRncm91cCwgcGxvdCA9IEZBTFNFLCBwcmVkSSA9IDIsIHBlcm1JID0gNTAwKQpgYGAKYGBge3J9CnBjYTMucGxvdCA8LSBwbG90X3BjYShwY2EzLCBoZXJyaW5nJGdyb3VwLCBhbm5vdGF0ZSA9ICJzdWJ0aXRsZSIpCnBsczMucGxvdCA8LSBwbG90X3Bsc2RhKHBsczMsIGFubm90YXRlID0gInN1YnRpdGxlIikKcGxvdF9ncmlkKGNvcjMsCiAgICAgICAgICBwY2EzLnBsb3QgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiTlVMTCIpLAogICAgICAgICAgcGxzMy5wbG90ICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIk5VTEwiKSwgbnJvdyA9IDEsIG5jb2wgPSAzKQpgYGAKCmBgYHtyfQpWSVBzMyA8LSBnZXRfVklQKHBsczMpICU+JQogIGFycmFuZ2UoZGVzYyhWSVApKQoKcGNhMy5sb2FkIDwtIGdldF9sb2FkaW5ncyhwY2EzKSAlPiUKICBzZWxlY3QoVmFyaWFibGUsIHAxLCBwMikgJT4lCiAgYXJyYW5nZShkZXNjKGFicyhwMSkpKQoKdGFibGUzIDwtIGZ1bGxfam9pbihWSVBzMywgcGNhMy5sb2FkKSAlPiUKICBhcnJhbmdlKFZhcmlhYmxlKSAlPiUgCiAgcmVuYW1lKGBQTFMtREEgVklQIHNjb3JlYCA9IFZJUCwgYFBDMSBsb2FkaW5nYCA9IHAxLCBgUEMyIGxvYWRpbmdgID0gcDIpICU+JSAKICBtdXRhdGVfaWYoaXMuZG91YmxlLCB+cm91bmQoLiw0KSkKdGFibGUzICU+JSBhcnJhbmdlKGRlc2MoYFBMUy1EQSBWSVAgc2NvcmVgKSkKdGFibGUzICU+JSBhcnJhbmdlKGRlc2MoYFBDMSBsb2FkaW5nYCkpCiMgd3JpdGVfZXhjZWxfY3N2KHRhYmxlMSwgImZpZ3MvdGFibGUxLmNzdiIpCgpgYGAKCg==